//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//
// (C) Copyright 2006 Marvell International Ltd.
// All Rights Reserved
//


#include <windows.h>
#include <ceddk.h>
#include "monahans.h"
#include "monahans_base_regs.h"
#include "xllp_defs.h"
#include "xllp_clkmgr.h"
#include "oal_args_kitl.h"

#include "clkmgr.h"

#define PRINT_TRACE_ON FALSE
#define PRINT_TRACE_ON_HIGH FALSE

typedef enum _tag_ClockReg
{
    CLKMGR_REG_FIRST  = 0,
    CLKMGR_REG_CKEN_A = CLKMGR_REG_FIRST,
    CLKMGR_REG_CKEN_B,
    CLKMGR_REG_OSCC,
    CLKMGR_REG_END
} CLKMGR_CLOCK_REG;

struct SRegisterInfo
{
    PHYSICAL_ADDRESS _addr;
    size_t _size;
    LPTSTR _evtName;
};
// IMPORTANT: must keep align with CLKMGR_CLOCK_REG definition
static const SRegisterInfo RegInfoMap[] =
{
    {{MONAHANS_BASE_REG_PA_CLKMGR, 0}, sizeof(XLLP_CLKMGR_T), TEXT("CLK_REG_CKEN_A")},
    {{MONAHANS_BASE_REG_PA_CLKMGR, 0}, sizeof(XLLP_CLKMGR_T), TEXT("CLK_REG_CKEN_B")},
    {{MONAHANS_BASE_REG_PA_OSCC, 0}, sizeof(XLLP_VUINT32_T), TEXT("CLK_REG_OSCC")}
};

#define CLKMGR_CLOCK_MASK_SIDECAR 0x200
#define CLKMGR_CLOCK_MASK_OSSC_POUT XLLP_OSCC_PEN
#define CLKMGR_CLOCK_MASK_MVED      (1<<11)

struct SClockInfo
{
    CLKMGR_CLOCK_TYPE _type;
    CLKMGR_CLOCK_REG _reg;
    DWORD _mask;
    LPTSTR _evtName;
};

// here is a fast map of the clock information
// IMPORTANT: should keep the same order with CLKMGR_CLOCK_TYPE defination
static const SClockInfo ClockInfoMap[] = 
{
    {CLKMGR_SIDECAR, CLKMGR_REG_CKEN_B, CLKMGR_CLOCK_MASK_SIDECAR, TEXT("CLK_SIDECAR")},
    {CLKMGR_OSSC_POUT, CLKMGR_REG_OSCC, CLKMGR_CLOCK_MASK_OSSC_POUT, TEXT("CLK_POUT")},
    {CLKMGR_MVED,      CLKMGR_REG_CKEN_B, CLKMGR_CLOCK_MASK_MVED,      TEXT("CLKMGR_MVED")}
};

class CGlobalConfiguration
{
public:
    enum
    {
        USB20_KITL = 0x00000001
    };
    static bool CheckAND(DWORD rules)
    {
        return RuleDispatch(rules, true);// "and" mode
    }
    static bool CheckOR(DWORD rules)
    {
        return RuleDispatch(rules, false);// "or" mode
    }
protected:
    static bool m_initialized;
    static bool checkUSB20KITL()
    {
        static int retvcache = 0;// 0: first time; 1: return true; 2 return false
        if (0 == retvcache)
        { // anyway we will only do this once, next time will use the last time value
          // no mater successfully or not
            HKEY Key;
            DWORD status;
            DWORD dwType;
            DWORD dwKITLCfg;
            DWORD dwSize;
            status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, OAL_KITL_REG_KEY_NAME, 0, KEY_ALL_ACCESS, &Key);
            if (status != ERROR_SUCCESS)
            { // default to not use usb 2.0 as kitl
                retvcache = 2;
                return false;
            }
            dwSize = sizeof(dwKITLCfg);
            status = RegQueryValueEx(Key, OAL_KITL_REG_VALUE_NAME_CFG, NULL, &dwType, (BYTE *)&dwKITLCfg, &dwSize);
            if (status != ERROR_SUCCESS)
            { // default to not use usb 2.0 as kitl
                retvcache = 2;
                return false;
            }
            if (dwType != REG_DWORD)
            { // default to not use usb 2.0 as kitl
                retvcache = 2;
                return false;
            }
            if((dwKITLCfg&OAL_KITL_REG_TYPE_MASK) == OAL_KITL_REG_TYPE_USB20)
            {
                RETAILMSG(1, (_T("EBOOT Config use USB 2.0 as KITL \r\n")));
                retvcache = 1;
                return true;
            }
            retvcache = 2;
            return false;
        }
        else if (1 == retvcache) return true;
        else return false;
    }
    static bool RuleDispatch(DWORD rules, bool UseAnd)
    {
        int i;
        DWORD bit;
        for (i = 0, bit = 1; i < 32; i++, bit<<=1)
        {
            if ((bit & rules) != 0)
            {
                bool roundVal=false;
                switch (bit & rules)
                {
                case USB20_KITL:
                    roundVal = checkUSB20KITL();
                    break;
                default:
                    break;
                }
                if (!UseAnd && roundVal)
                {// Use "or" mode, if any condition returns true, we should return true
                    return true;
                }
                else if (UseAnd && !roundVal)
                {// Use "and" mode, if any condition returns false, we should return false
                    return false;
                }
            }
        }
        // if we can reach here then every condition check won't chang default value
        // for "and" mode is every condition true, we should return true
        // for "or" mode is every condition false, we should return false
        return UseAnd;
    }
};
#define CLKMGR_ON true
#define CLKMGR_OFF false


class CClockSource
{
protected:
#pragma optimize( "", off )
    static void mask_op(bool B, XLLP_VUINT32_T* base, DWORD mask)
    {
        DWORD temp = *base;
        RETAILMSG(PRINT_TRACE_ON, (_T("[CLKMGR] before operation reg, 0x%08X \r\n"), temp));
        if (B)
            *base |= mask;
        else
            *base &= ~mask;
        temp = *base;
        RETAILMSG(PRINT_TRACE_ON_HIGH, (_T("[CLKMGR] after operation reg, 0x%08X \r\n"), temp));
    }
#pragma optimize( "", on )
    static void check_init()
    {
        if (!m_initialized)
        {
            RETAILMSG(PRINT_TRACE_ON, (_T("[CLKMGR] initialized cached_mmaps \r\n")));
            for (int i = CLKMGR_REG_FIRST; i < CLKMGR_REG_END; i++)
            {
                cached_mmaps[i] = NULL;
                cached_regevt[i] = NULL;
            }
            m_initialized = true;
        }
    }
    static PVOID MapClockReg(CLKMGR_CLOCK_TYPE type)
    {
        //check the input partameters, should never fail here
        //DEBUGCHK((type >= CLKMGR_TYPE_FIRST)&&(type < CLKMGR_TYPE_END));
        check_init();
        
        CLKMGR_CLOCK_REG regIndex = ClockInfoMap[type]._reg;
        
        //check the querred partameters, should never fail here
        //DEBUGCHK((regIndex >= CLKMGR_REG_FIRST)&&(regIndex < CLKMGR_REG_END));
        
        if (cached_mmaps[regIndex] == NULL)
        { // not mapped yet
            RETAILMSG(PRINT_TRACE_ON, (_T("[CLKMGR] cached_mmaps miss and map the IO spaces\r\n")));
            cached_mmaps[regIndex] = MmMapIoSpace(RegInfoMap[regIndex]._addr, RegInfoMap[regIndex]._size, FALSE);
            if ((regIndex == CLKMGR_REG_CKEN_A) || (regIndex == CLKMGR_REG_CKEN_B))
            { // the two share the same IO page
                RETAILMSG(PRINT_TRACE_ON, (_T("[CLKMGR] CLKMGR_REG_CKEN_A and CLKMGR_REG_CKEN_A share IO spaces\r\n")));
                void * vaddr = cached_mmaps[regIndex];
                cached_mmaps[CLKMGR_REG_CKEN_A] = vaddr;
                cached_mmaps[CLKMGR_REG_CKEN_B] = vaddr;
                // now we have CKEN_A and CKEN_B mapped to the same page
            }
        }
        return cached_mmaps[regIndex];
    }
    static void UnMapClockReg(CLKMGR_CLOCK_TYPE type, void * addr)
    {
        //check the input partameters, should never fail here
        //DEBUGCHK((type >= CLKMGR_TYPE_FIRST)&&(type < CLKMGR_TYPE_END));
        CLKMGR_CLOCK_REG regIndex = ClockInfoMap[type]._reg;
        //check the querred partameters, should never fail here
        //DEBUGCHK((regIndex >= CLKMGR_REG_FIRST)&&(regIndex < CLKMGR_REG_END));
        RETAILMSG(PRINT_TRACE_ON, (_T("[CLKMGR] unmap the IO spaces\r\n")));
        MmUnmapIoSpace(addr, RegInfoMap[regIndex]._size);
    }
private:
    static bool m_initialized;
    static void * cached_mmaps[CLKMGR_REG_END];
    static HANDLE cached_regevt[CLKMGR_REG_END];
public:
    static DWORD GetID()
    {
        static DWORD bound = 1;
        if (bound == 0)
        {
            return 0;
        }
        return bound++;
    }


    static void OperateClock(CLKMGR_CLOCK_TYPE type, bool turn)
    {
        //check the input partameters, should never fail here
        //DEBUGCHK((type >= CLKMGR_TYPE_FIRST)&&(type < CLKMGR_TYPE_END));
        check_init();
        CLKMGR_CLOCK_REG regIndex = ClockInfoMap[type]._reg;
        
        // we have to add global access control for each register to avoid conflict
        // although the possible conflicts is very unusual
        if (cached_regevt[regIndex] == NULL)
        { // try to get the event
            // the operation may fail, but we will do nothing about it
            // the fail-safe would be no access control, however this would be
            // one of the most unlikely things to happen
            cached_regevt[regIndex] = CreateEvent(NULL, FALSE, TRUE, RegInfoMap[regIndex]._evtName);
            if (cached_regevt[regIndex] == NULL)
            {
                RETAILMSG(1, (_T("[CLKMGR] unexpected error - can not create reg event\r\n")));
            }
            else
            {
                RETAILMSG(PRINT_TRACE_ON, (_T("[CLKMGR] successfully get the reg event\r\n")));
            }
        }
        if (cached_regevt[regIndex] != NULL)
        { // the event might be valid, if so we will omit the access control
            // if the first time use this, the event should in status "set" so we can pass this
            // if not, the status relie on usage status
            WaitForSingleObject(cached_regevt[regIndex], INFINITE);
        }
        // MapClockReg will try to query a cached map or create one when not exist
        // we need to do the map under access protect also
        XLLP_VUINT32_T * base = (XLLP_VUINT32_T *)MapClockReg(type);
        if (base != NULL)
        {// if returns NULL means map failed, we have to omit the operation
            switch (regIndex)
            {
            case CLKMGR_REG_CKEN_A:
                {
                    base = &((XLLP_CLKMGR_T *)base)->d0cken_a;
                    break;
                }
            case CLKMGR_REG_CKEN_B:
                {
                    base = &((XLLP_CLKMGR_T *)base)->d0cken_b;
                    break;
                }
            case CLKMGR_REG_OSCC:
                { // in this case "base" should be what we want
                    break;
                }
            default:
                //DEBUGCHK(FALSE);
                break;
            }
            RETAILMSG(PRINT_TRACE_ON, (_T("[CLKMGR] turn %s type %d in reg %d\r\n"), turn?_T("on"):_T("off"), type, regIndex));
            if (turn)
            { // turn on clock, do it anyway
                mask_op(turn, base, ClockInfoMap[type]._mask);
            }
            else
            { // if we are trying to turn off one clock 
                switch (type)
                {
                case CLKMGR_SIDECAR:
                case CLKMGR_OSSC_POUT:
                    {// for sidecar and clk_pout
                        if (!CGlobalConfiguration::CheckOR(CGlobalConfiguration::USB20_KITL))
                        { //if we are not use USB20 as KITL, we can do the operation
                            mask_op(turn, base, ClockInfoMap[type]._mask);
                        }
                        else
                        { // to keep usb20 operate normally as KITL, we can not disable the clock
                            RETAILMSG(PRINT_TRACE_ON_HIGH, (_T("[CLKMGR] disfunc close clk due to USB2.0 as KITL\r\n")));
                        }
                        break;
                    }
                default:
                    {// other clock
                        mask_op(turn, base, ClockInfoMap[type]._mask);
                        break;
                    }
                }
            }
        }
        else
        { // we can do nothing here
            RETAILMSG(PRINT_TRACE_ON, (_T("[CLKMGR] map the IO spaces fail\r\n")));
        }
 
        if (cached_regevt[regIndex] != NULL)
        { // the event might be valid, if so we will omit the access control
            // reset the event status
            SetEvent(cached_regevt[regIndex]);
        }
    }
};

void * CClockSource::cached_mmaps[CLKMGR_REG_END];
HANDLE CClockSource::cached_regevt[CLKMGR_REG_END];
bool CClockSource::m_initialized = false;


class CSyncGuard
{
private:
    CRITICAL_SECTION * m_pcs;
public:
    CSyncGuard(CRITICAL_SECTION * pcs)
    {
        m_pcs = pcs;
        EnterCriticalSection(m_pcs);
    }
    virtual ~CSyncGuard()
    {
        LeaveCriticalSection(m_pcs);
    }
};



CClockManager::_ERRTYPE CClockManager::prepare_or_abort()
{
    if (m_hEvent == NULL)
    {
        m_hEvent = CreateEvent(NULL, FALSE, TRUE, ClockInfoMap[m_type]._evtName);
        if (m_hEvent == NULL)
        {
            RETAILMSG(1,(_T("[CLKMGR %d] cannot get required event\r\n"), m_instance));
            return ERROR_GET_EVENT;
        }
        DWORD status = GetLastError();
        // check if the first time open this event
        // here we have to do this in a process level mutually exclusive
        WaitForSingleObject(m_hEvent, INFINITE);
        
        // only one will get here at a time
        if (status != ERROR_ALREADY_EXISTS)
        {// first open the event, trying to init the over all count
            RETAILMSG (PRINT_TRACE_ON, (_T("[CLKMGR %d] Initializing event data\r\n"), m_instance));
            SetEventData(m_hEvent, 0);
        } else
        {// nothing need to do
            RETAILMSG (PRINT_TRACE_ON, (_T("[CLKMGR %d] Event handle already exists.  Not initializing event data.\r\n"), m_instance));
        }
        
        // reset event so that next one can come in here
        SetEvent(m_hEvent);
    }
    return SUCCESS;
}
CClockManager::_ERRTYPE CClockManager::control(bool en)
{
    // this is a thread synchronized method, here we have to control this for reference count maintain
    CSyncGuard g(&m_cs);
    if (!m_valid)
    { // the initialized type is invalid, just refuse to do anything
        RETAILMSG (1, (_T("[CLKMGR %d] use clkmgr with invalid type indicator\r\n"), m_instance));
        return ERROR_INVALID_TYPE;
    }
    _ERRTYPE retval = prepare_or_abort();
    if (retval != SUCCESS)
    { // abort the operation
        return retval;
    }
    // change refrence count anyway, we will adjust it later
    // if the routine can be here, which means that every required resource is ready now
    // so we will omit to check these resource again
    RETAILMSG (PRINT_TRACE_ON, (_T("[CLKMGR %d] object reference count before %s operation is %d\r\n"), m_instance, en?_T("on"):_T("off"), m_refCnt));
    int oldRefCnt = m_refCnt;
    m_refCnt += en?1:-1;
    if ((oldRefCnt + m_refCnt) == 1)
    {   // the condition ensures either ((oldRefCnt == 0) && (m_refCnt == 1)) or ((oldRefCnt == 1) && (m_refCnt == 0))
        // which mean we should either turn on or turn off the clock
        // process level mutually exclusive for overall ref count access
        RETAILMSG (PRINT_TRACE_ON, (_T("[CLKMGR %d] trying to turn %s the clock\r\n"), m_instance, en?_T("on"):_T("off")));
        WaitForSingleObject(m_hEvent, INFINITE);
        DWORD oldGCnt = GetEventData(m_hEvent);
        RETAILMSG (PRINT_TRACE_ON, (_T("[CLKMGR %d] current global reference before count is %d\r\n"), m_instance, oldGCnt));
        DWORD newGCnt = oldGCnt + (en?1:-1);
        if ((newGCnt + oldGCnt) == 1)
        { // need to do stuff
            RETAILMSG (PRINT_TRACE_ON, (_T("[CLKMGR %d] yes, need to do the turn %s operation\r\n"), m_instance, en?_T("on"):_T("off")));
            CClockSource::OperateClock(m_type, en?CLKMGR_ON:CLKMGR_OFF);
        }
        else if (oldGCnt == 0)
        {   // if oldGCnt is 0, here newGCnt should be (DWORD)(-1)
            // the outter loop logic would never let this happen
            // anyway we just put code here to "ensure"
            newGCnt = 0;
            RETAILMSG(1,(_T("[CLKMGR %d] unmatched enable/disable on multiple source detected\r\n"), m_instance));
        }
        // adjust reference count
        SetEventData(m_hEvent, newGCnt);
        RETAILMSG (PRINT_TRACE_ON_HIGH, (_T("[CLKMGR %d] current global reference after count is %d\r\n"), m_instance, newGCnt));
        // release the mutually exclusive access
        SetEvent(m_hEvent);
    }
    else if (m_refCnt < 0)
    { // should not be here, the invocation is not correct, reset the counter
        m_refCnt = 0;
        RETAILMSG(1,(_T("[CLKMGR %d] unmatched enable/disable on one source detected\r\n"), m_instance));
    }
    RETAILMSG (PRINT_TRACE_ON, (_T("[CLKMGR %d] object reference count after %s operation is %d\r\n"), m_instance, en?_T("on"):_T("off"), m_refCnt));
    return retval;
}

CClockManager::CClockManager(CLKMGR_CLOCK_TYPE type)
{
    m_type = type;
    m_hEvent = NULL;
    m_refCnt = 0;
    m_valid = true;
    m_instance = CClockSource::GetID();
    InitializeCriticalSection(&m_cs);
    if (!(type >= CLKMGR_TYPE_FIRST)&&(type < CLKMGR_TYPE_END))
    {
        RETAILMSG(1,(_T("[CLKMGR %d] trying to require invalid type of clock\r\n"), m_instance));
        m_valid = false;
    }
}

CClockManager::~CClockManager()
{
    // actually the program would never reach here
    if (m_hEvent != NULL)
    {
        // we maynot need this code because this would cause destruction of the kernel object
        // if no process or thread keep a reference. This would let the event data loss
        //CloseHandle(m_hEvent);
    }
    DeleteCriticalSection(&m_cs);
}


extern "C" int InitClkMgr(CLKMGR_CLOCK_TYPE type, CLKMGR *clkmgr)
{
    CClockManager * p = new CClockManager(type);
    if (clkmgr == NULL)
    {
        return FALSE;
    }
    *clkmgr = p;
    if (p == NULL)
    {
        return FALSE;
    }
    return TRUE;
}

extern "C" void ReleaseClkMgr(CLKMGR *clkmgr)
{
    if (clkmgr == NULL)
    {
        return ;
    }
    if (*clkmgr != NULL)
    {
        delete ((CClockManager *)(*clkmgr));
    }
    *clkmgr = NULL;
}

extern "C" void EnableClock(CLKMGR *clkmgr)
{
    if (clkmgr == NULL)
    {
        return ;
    }
    if (*clkmgr != NULL)
    {
        ((CClockManager *)(*clkmgr))->enable();
    }
}

extern "C" void DisableClock(CLKMGR *clkmgr)
{
    if (clkmgr == NULL)
    {
        return ;
    }
    if (*clkmgr != NULL)
    {
        ((CClockManager *)(*clkmgr))->disable();
    }
}


